Repository navigation
fix(confirm-write): render write args as a table, and label buttons by verb - #19
Merged
Merged
Conversation
The confirm_write card serializes request.args with json.dumps, so the approver sees a ~15-field JSON line that Slack collapses behind Show more -- they cannot read the payload they are authorizing. Design a Python/TypeScript seam where the agent decides which fields to show and the card decides how, rendering a headerless Table that both the Slack and Teams renderers support natively. Refs CPK-7748
The approval card serialized request.args with json.dumps, so a Linear project write rendered as a ~15-field JSON line that Slack collapsed behind Show more. An approver who cannot read the payload cannot meaningfully approve it, which defeats the human-in-the-loop gate. Split the concern: summarize_args decides which fields are worth showing and stringifies them, ConfirmWrite decides how they look. Empty values are dropped by an explicit membership test rather than falsiness, so a priority of 0 and a False flag survive. The card renders a headerless two-column Table, which both the Slack Block Kit and Teams Adaptive Card renderers support natively. The legacy detail string stays accepted so a stale agent revision still renders a body across a deploy skew. For the payload in the report, 15 fields become 7 readable rows. Refs CPK-7748
Deletes go through the same interceptor as creates, so a delete_customer call rendered a green Create button under "Nothing is written until you click Create" -- inviting styling and false wording in front of an irreversible operation. Derive the button and lock line from the action's leading verb, and reword to "Nothing is changed", which holds for deletes and updates too. Destructive verbs take the warning colour and drop Cancel to neutral so the red marks the irreversible choice rather than the safe one. "Cancel subscription" derives the label Cancel and would render two identical buttons, one cancelling the subscription and one cancelling the request; that case falls back to Confirm. The destructive flag reads the real verb rather than the substituted label, so relabelling cannot silently cost the styling. Refs CPK-7748
tylerslaton
approved these changes
Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the
confirm_writeapproval card, which serialized the mutating tool's arguments withjson.dumpsand rendered them as a raw JSON line. For a Linear project write that was ~15 fields long enough that Slack collapsed it behind Show more — so the approver could not see the payload they were authorizing. That defeats the point of the human-in-the-loop gate.Reported in Slack; tracked as CPK-7748.
Before / after
The exact payload from the report, 15 fields, collapsed mid-blob:
now renders as a native table of 7 rows:
How it works
The agent decides what to show; the card decides how.
summarize_argsinagent/write_confirmation.pyfilters and stringifies;ConfirmWriterenders. Both are independently testable and the card stays presentational."",[],{},None. The check is explicit membership, not Python falsiness, sopriority: 0andFalsesurvive. Dropping numeric zero would silently misrepresent a real Linear value ("No priority").addTeams→ "Add teams".… | N more fieldsnote, so nothing is withheld silently.<Table>is native on both surfaces — a Block Kittableblock on Slack, an Adaptive CardTableon Teams. Headerless, since a "Field | Value" header row would spend a row restating the layout.detailstring is still accepted, so a stale agent revision renders a body rather than an empty card across a deploy skew.Delete correctness
Checking the table against a delete surfaced a second, worse problem. Deletes go through the same interceptor, so
delete_customerrendered a green Create button under "Nothing is written until you click Create" — inviting styling and false wording in front of an irreversible operation.Createprimary,CanceldangerUpdateprimary,CanceldangerDeletedanger,CancelneutralArchivedanger,CancelneutralConfirmdanger,CancelneutralCancel subscriptionwould derive the label "Cancel" and render two identical buttons, one cancelling the subscription and one cancelling the request. That case falls back toConfirm, and the destructive flag reads the real verb rather than the substituted label so relabelling cannot silently cost the styling.Tests
11 new tests, TDD throughout — every one watched failing first, for the right reason.
0/Falseretention, key humanizing, list joining, nested compaction, truncation, the row capfirstRowAsHeader: false, legacydetailfallback, schema rejection of malformed fields, channel wiring, verb labelling, destructive styling, the Cancel collisionCI will be red — not from this change
maindoes not pass its own suite after the 0.6.0 / 1.65.0 bump: 9 failing tests and 20 type errors on cleanb474c17oncepnpm installruns. Filed as CPK-7751 with three root causes (a stale version-pin guard, removed/renamedchannelstype surface, and a new Intelligence API key format check).Verified by stashing: the same 9 failures and 20 type errors before and after this branch. This PR adds zero new ones — 130 → 141 passing, 20 → 20 type errors.
Not in scope
{"id": "3f34e790-…"}, so the table honestly showsId | 3f34e790-…— readable, but it doesn't say what is being deleted. Fixing that needs the agent to resolve the id before pausing, which is agent-side work. Worth its own ticket.